package ggaviator

import (
	"context"
	"encoding/json"
	"github.com/shopspring/decimal"
	"go.uber.org/zap"
	"google.golang.org/protobuf/proto"
	"strconv"
	"strings"
	"time"
	"wzgames/internal/client"
	"wzgames/internal/codec/sfs"
	"wzgames/internal/constant"
	"wzgames/internal/gloader"
	"wzgames/internal/gvalidator"
	"wzgames/internal/model/aviatorp"
	"wzgames/internal/model/gamep"
	"wzgames/internal/model/mcomm"
	"wzgames/internal/model/statement"
	"wzgames/internal/response"
	"wzgames/internal/services/games"
	"wzgames/pkg/logger"
	"wzgames/pkg/utils/byteutil"
	"wzgames/pkg/utils/calcutil"
	"wzgames/pkg/utils/dateutil"
	"wzgames/pkg/utils/stringutil"
)

// 小飞机

type GAviator struct {
	games.GGame
	zn string

	cfg       *aviatorp.AviatorConfig
	airCraft  *AirCraft
	instIdStr string

	// nats keys
	keyBet     string
	keyCancel  string
	keyCashOut string
	keyAct     string
}

func NewAviator(inst *gamep.GameInst) (games.IGameService, error) {
	av := &GAviator{
		instIdStr: strconv.FormatInt(inst.InstID, 10),
	}
	av.zn = "aviator"
	av.SetGameInst(inst)
	err := av.updateConf()
	if err != nil {
		return av, err
	}
	av.airCraft = newAirCraft(av)
	err = av.airCraft.Init(av.cfg)
	av.keyBet = constant.AvtBetNKey + "." + av.instIdStr
	av.keyCancel = constant.AvtCancelNKey + "." + av.instIdStr
	av.keyCashOut = constant.AvtCashOutNKey + "." + av.instIdStr
	av.keyAct = constant.AvtActNKey + "." + av.instIdStr
	return av, err
}

func init() {
	// 注册构造
	games.RegisterGame(gamep.AviatorGameId, NewAviator)
	games.RegisterMarker(gamep.AviatorGameId, NewAviatorMarker)
}

func (a *GAviator) updateConf() error {
	a.Lock()
	defer a.Unlock()
	if a.cfg == nil {
		a.cfg = &aviatorp.AviatorConfig{}
	}
	err := proto.Unmarshal(a.GameInst.Conf, a.cfg)
	if err != nil {
		logger.Error("[Aviator] update Conf on proto.Unmarshal", zap.Error(err))
		return err
	}
	return nil
}

// Update 更新小飞机实例的配置
func (a *GAviator) Update() {
	err := a.updateConf()
	if err == nil {
		a.airCraft.Update(a.cfg)
	}
}

func (a *GAviator) Destroy() {
	if a.airCraft != nil {
		a.airCraft.Destroy()
	}
}

func (a *GAviator) Login(tb *games.GameTable) {
	code := tb.InitBalance()
	msg := response.AcquireMsgO()
	msg.Id = 1
	msg.ControlId = 0
	xw := sfs.XWriter{}
	defer xw.Reset()
	if code != 0 {
		xw.WriteUShort(uint16(2))
		xw.WriteShortS(sfs.KeyErrorCode, code)
		xw.WriteUtfStringArray(sfs.KeyErrorParams, []string{})
	} else {
		xw.WriteUShort(uint16(6))
		xw.WriteShortS(sfs.KeyReconnectionSeconds, 0)
		xw.WriteStringS(sfs.KeyZoneName, a.zn)
		xw.WriteLongS(sfs.KeyId, int64(tb.Player.ID()))
		xw.WriteStringS(sfs.KeyUserName, tb.Player.ShowName())
		xw.WriteShortS(sfs.KeyPrivilegeId, 0)
		xw.WriteSfsArrayPre(sfs.KeyRoomList, 0)
	}

	xw.WriteTo(msg)
	tb.Send2Client(msg)
	// 玩家信息缓存
	tb.Marker.(*AviatorMarker).SetAvBetU(&gamep.BetUser{
		AgtId:    tb.Player.AgtID(),
		AgtKey:   tb.Player.AgtKey(),
		Uid:      tb.Player.ID(),
		Uname:    tb.Player.ShowName(),
		Uhead:    tb.Player.UHead(),
		Currency: tb.Seat.Currency,
	})
	a.clientInit(tb)

	a.airCraft.Register(tb)
}

func (a *GAviator) Logout(tb *games.GameTable) {
	a.airCraft.UnRegister(tb) // 管理中注销
	canCancel, currRoundId := a.airCraft.CanBet()
	avtMarker := tb.Marker.(*AviatorMarker)
	if canCancel {
		// 登出判断是否可以取消投注 帮取消
		if avtMarker.CheckCancel(BetIdx1, currRoundId) {
			_, betObj, avBet := avtMarker.GetBetNode(BetIdx1)
			betObj.GameAct = mcomm.GameAction_GameCancel
			a.doSubmit(tb, betObj, avBet)
		}
		if avtMarker.CheckCancel(BetIdx2, currRoundId) {
			_, betObj, avBet := avtMarker.GetBetNode(BetIdx2)
			betObj.GameAct = mcomm.GameAction_GameCancel
			a.doSubmit(tb, betObj, avBet)
		}
	}

	canOut, currRoundId, multi, maxMulti := a.airCraft.CanCashOut()
	if canOut {
		// 登出判断是否可以CashOut 帮CashOut
		if avtMarker.CheckCashOut(BetIdx1, currRoundId) {
			_, betObj, avBet := avtMarker.GetBetNode(BetIdx1)
			d1, _ := decimal.NewFromString(betObj.BetAmt)
			dm := decimal.NewFromFloat(multi)
			ret := d1.Mul(dm).Truncate(tb.Seat.Precision)
			avBet.Multiplier = multi
			avBet.Result = maxMulti
			betObj.ImRet.Win = mcomm.GameResult_GameWin
			betObj.ImRet.Payout = dm.String()
			betObj.ImRet.WinAmt = ret.String()
			betObj.ImRet.Dateline = dateutil.UnixMilli()
			betObj.GameAct = mcomm.GameAction_GameCashOut
			betObj.ImRet.GameAct = betObj.GameAct
			a.doSubmit(tb, betObj, avBet)
		}
		if avtMarker.CheckCashOut(BetIdx2, currRoundId) {
			_, betObj, avBet := avtMarker.GetBetNode(BetIdx2)
			d1, _ := decimal.NewFromString(betObj.BetAmt)
			dm := decimal.NewFromFloat(multi)
			ret := d1.Mul(dm).Truncate(tb.Seat.Precision)
			avBet.Multiplier = multi
			avBet.Result = maxMulti
			betObj.ImRet.Win = mcomm.GameResult_GameWin
			betObj.ImRet.Payout = dm.String()
			betObj.ImRet.WinAmt = ret.String()
			betObj.ImRet.Dateline = dateutil.UnixMilli()
			betObj.GameAct = mcomm.GameAction_GameCashOut
			betObj.ImRet.GameAct = betObj.GameAct
			a.doSubmit(tb, betObj, avBet)
		}
	}
}

func (a *GAviator) InstIdStr() string {
	return a.instIdStr
}

// getOnlines 从缓存获取在线人数
func (a *GAviator) getOnlines() int64 {
	var sb strings.Builder
	sb.WriteString(gloader.EnvString())
	sb.WriteString(constant.GameOnlineKey)
	sb.WriteString(a.instIdStr)
	ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
	s, err := gloader.CommMgr().RedisPool().Get().Get(ctx, sb.String()).Result()
	cancel()
	if err != nil {
		logger.Error("[Aviator] get onlines fail on Get", zap.Int64("InstID", a.GameInstId()), zap.Error(err))
		return 0
	}
	r, _ := stringutil.ToInt64(s, 0)
	return r
}

// 发往客户端配置数据
func (a *GAviator) clientInit(tb *games.GameTable) {
	msg := response.AcquireMsgO13()
	xw := sfs.XWriter{}
	defer xw.Reset()
	xw.WriteUShort(uint16(2))
	xw.WriteStringS(sfs.CmdKey, constant.KeyInit)
	xw.WriteSfsObjectPre(sfs.ParamId, uint16(16))
	xw.WriteShortS(sfs.KeyCode, response.CSuccess)
	xw.WriteIntS(constant.KeyValType, int32(tb.GameCurr.VType))
	roundId, roundState := a.airCraft.RoundInfo()
	xw.WriteLongS(constant.KeyOnlinePlayers, a.getOnlines())
	xw.WriteLongS(constant.KeyRoundId, roundId)
	xw.WriteIntS(constant.KeyStageId, roundState)
	xw.WriteDoubleS(constant.KeyCurrentMultiplier, a.airCraft.CurrentMultiplier())
	xw.Write(a.getFlewsFromCache()) // 历史轮次
	xw.WriteSfsArrayPre(constant.KeyActiveBets, uint16(0))
	xw.WriteSfsArrayPre(constant.KeyActiveFreeBetsInfo, uint16(0))
	xw.WriteSfsArrayPre(constant.KeyBets, uint16(0))
	xw.WriteSfsArrayPre(constant.KeyCashouts, uint16(0))

	xw.WriteSfsObjectPre(constant.KeyUser, uint16(6))
	xw.WriteDoubleS(constant.KeyBalance, tb.Balance())
	xw.WriteStringS(constant.KeyCurrency, tb.Seat.Currency)
	xw.WriteStringS(constant.KeyProfileImage, tb.Player.UHead())
	xw.WriteStringS(constant.KeyUserId, tb.Player.ShowID())
	xw.WriteStringS(constant.KeyUsername, tb.Player.ShowName())
	xw.WriteSfsObjectPre(constant.KeySettings, uint16(4))
	xw.WriteBoolS(constant.KeyMusic, false)
	xw.WriteBoolS(constant.KeySound, false)
	xw.WriteBoolS(constant.KeySecondBet, true)
	xw.WriteBoolS(constant.KeyAnimation, true)

	xw.Write(a.cfg.ConfigBin)
	xw.WriteStringS(constant.KeyCurrency, tb.Seat.Currency)
	xw.WriteIntS(constant.KeyBetPrecision, tb.Seat.Precision)

	xw.WriteSfsObjectPre(constant.KeyAutoCashOut, uint16(3))
	if tb.GameCurr.VType == mcomm.ValueType_Float64Type {
		xw.WriteDoubleS(constant.KeyMinValue, tb.GameCurr.FloatValueLimit.AutoCashOutMin)
		xw.WriteDoubleS(constant.KeyMaxValue, tb.GameCurr.FloatValueLimit.AutoCashOutMax)
		xw.WriteDoubleS(constant.KeyDefaultValue, tb.GameCurr.FloatValueLimit.AutoCashOutDefault)
		xw.WriteDoubleArrayS(constant.KeyBetOptions, tb.GameCurr.FloatValueLimit.FastBets)
		xw.WriteDoubleS(constant.KeyMaxBet, tb.GameCurr.FloatValueLimit.MaxBet)
		xw.WriteDoubleS(constant.KeyMinBet, tb.GameCurr.FloatValueLimit.MinBet)
		xw.WriteDoubleS(constant.KeyDefaultBetValue, tb.GameCurr.FloatValueLimit.DefaultBetValue)
		xw.WriteDoubleS(constant.KeyMaxUserWin, tb.GameCurr.FloatValueLimit.MaxUserWin)
		xw.WriteDoubleS(constant.KeyBetInputStep, tb.GameCurr.FloatValueLimit.InputStep)
	} else {
		xw.WriteStringS(constant.KeyMinValue, tb.GameCurr.StrValueLimit.AutoCashOutMin)
		xw.WriteStringS(constant.KeyMaxValue, tb.GameCurr.StrValueLimit.AutoCashOutMax)
		xw.WriteStringS(constant.KeyDefaultValue, tb.GameCurr.StrValueLimit.AutoCashOutDefault)
		xw.WriteUtfStringArray(constant.KeyBetOptions, tb.GameCurr.StrValueLimit.FastBets)
		xw.WriteStringS(constant.KeyMaxBet, tb.GameCurr.StrValueLimit.MaxBet)
		xw.WriteStringS(constant.KeyMinBet, tb.GameCurr.StrValueLimit.MinBet)
		xw.WriteStringS(constant.KeyDefaultBetValue, tb.GameCurr.StrValueLimit.DefaultBetValue)
		xw.WriteStringS(constant.KeyMaxUserWin, tb.GameCurr.StrValueLimit.MaxUserWin)
		xw.WriteStringS(constant.KeyBetInputStep, tb.GameCurr.StrValueLimit.InputStep)
	}

	xw.WriteTo(msg)
	tb.Send2Client(msg)
}

func (a *GAviator) getFlewsFromCache() []byte {
	var sb strings.Builder
	sb.WriteString(constant.AvtFlewsKey)
	sb.WriteString(a.instIdStr)
	ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
	ret, err := gloader.CommMgr().RedisPool().Get().Get(ctx, sb.String()).Result()
	cancel()
	if err != nil {
		logger.Error("[Aviator] get flews from cache fail on Get", zap.Int64("InstID", a.GameInstId()), zap.Error(err))
		return []byte{}
	}
	return byteutil.StringToSliceByte(ret)
}

func (a *GAviator) DoMessage(tb *games.GameTable, data interface{}) {
	switch m := data.(type) {
	case *sfs.TSfsObject:
		code, cmd := a.CheckMessage(m, tb.Player.AgtID(), tb.InstID(), tb.Player.ID())
		if code != 0 {
			return
		}
		switch cmd {
		case constant.HandlerServerSeed: // 服务端种子
			a.ServerSeedHandler(tb)
		case constant.HandlerBet: // bet
			a.betHandler(tb, m)
		case constant.HandlerCashOut: // cashOut
			a.cashOutHandler(tb, m)
		case constant.HandlerCancelBet: // cancel bet
			a.cancelBetHandler(tb, m)
		case constant.HandlerUpdateBalance: // 获取新的余额
			a.updateBalanceHandler(tb)
		case constant.HandlerRoundFairness: // 轮次详情
			a.roundFairnessHandler(tb, m)
		case constant.HandlerCurrentBetsInfo:
		case constant.HandlerPreviousRoundInfo:
		case constant.HandlerBetHistory, constant.HandlerGetHistory: // 投注历史
			a.getBetHistory(tb, m)
		default:
			logger.Warn("[Aviator] invalid cmd key", zap.Int64("AgtId", tb.Player.AgtID()), zap.Uint64("Uid", tb.Player.ID()), zap.Int64("InstID", tb.InstID()), zap.String("CmdKey", cmd))
		}
	case *games.AutoCashOutCheckPoint: // 自动CashOut处理
		a.doAutoCashOut(m)
	default:
		logger.Warn("[Aviator] unknown message on DoMessage", zap.Int64("AgtId", tb.Player.AgtID()), zap.Uint64("Uid", tb.Player.ID()), zap.Int64("InstID", tb.InstID()))
	}
}

func (a *GAviator) ServerSeedHandler(tb *games.GameTable) {
	msg := response.AcquireMsgO13()
	xw := sfs.XWriter{}
	defer xw.Reset()
	xw.WriteUShort(uint16(2))
	xw.WriteStringS(sfs.CmdKey, constant.CmdNextServerSeed)
	xw.WriteSfsObjectPre(sfs.ParamId, uint16(2))
	xw.WriteShortS(sfs.KeyCode, response.CSuccess)
	xw.WriteStringS(constant.KeyServerSeedSHA256, a.airCraft.NextRoundSeedH256())

	xw.WriteTo(msg)
	tb.Send2Client(msg)
}

func (a *GAviator) updateBalanceHandler(tb *games.GameTable) {
	msg := response.AcquireMsgO13()
	xw := sfs.XWriter{}
	defer xw.Reset()
	xw.WriteUShort(uint16(2))
	xw.WriteStringS(sfs.CmdKey, constant.CmdNewBalance)
	xw.WriteSfsObjectPre(sfs.ParamId, uint16(2))
	xw.WriteShortS(sfs.KeyCode, response.CSuccess)
	xw.WriteDoubleS(constant.CmdNewBalance, tb.Balance())

	xw.WriteTo(msg)
	tb.Send2Client(msg)
}

func (a *GAviator) betHandler(tb *games.GameTable, exMsg *sfs.TSfsObject) {
	data := exMsg.GetContent()
	if data == nil {
		a.RetErr(tb, response.ErrBadRequest, constant.CmdBet)
		return
	}
	data = data.GetSFSObject(sfs.ParamId)
	if data == nil {
		a.RetErr(tb, response.ErrBadRequest, constant.CmdBet)
		return
	}
	clientSeed := data.GetUtfString(constant.KeyClientSeed)
	if !gvalidator.IsAlphaNumeric(clientSeed) || len(clientSeed) > 32 {
		a.RetErr(tb, response.ErrBadRequest, constant.CmdBet)
		return
	}
	betIdStr := data.GetUtfString(constant.KeyBetId) // 投注位
	if betIdStr != BetIdx1 && betIdStr != BetIdx2 {
		a.RetErr(tb, response.ErrBadRequest, constant.CmdBet)
		return
	}
	betValue := data.GetUtfString(constant.KeyBet)
	if !gvalidator.IsAmount(betValue) || !gloader.CommMgr().CheckAmountPointByCurrency(tb.Seat.Currency, betValue) {
		a.RetErr1(tb, response.ErrAmount, constant.CmdBet, betIdStr)
		return
	}
	// 是否设置自动CashOut
	strAutoCashOut := data.GetUtfString(constant.KeyCashOutLimit)
	if strAutoCashOut == constant.ZeroStr || strAutoCashOut == constant.EmptyStr || strAutoCashOut == constant.ZeroDot {
		strAutoCashOut = constant.ZeroF
	}
	if !gvalidator.IsAmount(strAutoCashOut) || !calcutil.CheckAmountDecimalPoint(strAutoCashOut, a.cfg.GameConfig.MultiplierPrecision) {
		a.RetErr1(tb, response.ErrAutoCash, constant.CmdBet, betIdStr)
		return
	}
	autoMultiple, _ := stringutil.ToFloat64(strAutoCashOut, 0.0)
	if autoMultiple > 0 && autoMultiple <= 1.0 { // 无效的自动CashOut设置
		a.RetErr1(tb, response.ErrAutoCash, constant.CmdBet, betIdStr)
		return
	}
	// 初始化投注对象，检查投注额度是否合法
	betObj := tb.GenBetObj(mcomm.GameAction_GameBet)
	balaCode := tb.CheckBetValue(betValue)
	if balaCode != 0 {
		if balaCode == response.ErrInsufficientBalance {
			balaCode = tb.LoadBalance()
		}
		if balaCode == 0 {
			balaCode = tb.CheckBetValue(betValue)
		}
		if balaCode != 0 {
			betObj.CkCode = int32(balaCode)
			betObj.Balance = tb.BalanceStr()
			tb.PlayInputData(betObj, exMsg) // 记录原始投注数据
			a.RetErrA(tb, balaCode, constant.CmdBet, betIdStr, true)
			return
		}
	}

	canBet, currRoundId := a.airCraft.CanBet()
	if !canBet {
		betObj.CkCode = int32(response.ErrForbidden)
		tb.PlayInputData(betObj, exMsg)
		a.RetErr1(tb, response.ErrForbidden, constant.CmdBet, betIdStr)
		return
	}
	avtMarker := tb.Marker.(*AviatorMarker)
	if !avtMarker.CheckIdxPlay(betIdStr, currRoundId) {
		a.RetErr1(tb, response.ErrFrequence, constant.CmdBet, betIdStr)
		return
	}
	betObj.RoundId = currRoundId
	tb.PlayInputData(betObj, exMsg)

	betId := tb.GenBetNo()
	if betId == 0 {
		a.RetErr(tb, response.ErrInternal, constant.CmdBet)
		avtMarker.ClearIdxPlay(betIdStr)
		return
	}
	tb.UpBetObj(betObj)
	betN := &aviatorp.BetNote{
		BetU:       avtMarker.GetAvBetU(),
		ClientSeed: clientSeed,
		BetId:      betIdStr,
		BetStr:     betValue,
		FreeBet:    data.GetBool("freeBet"),
		Dateline:   dateutil.UnixMilli(),
		RoundId:    currRoundId,
		BetNo:      betId,
		Op:         constant.AvtBetBet,
	}
	betObj.BetId = betId
	betObj.BetAmt = betValue
	betObj.EffAmt = betObj.BetAmt
	betObj.FreeBet = betN.FreeBet
	avBet := &statement.AviatorBetInfo{
		ServerSeed: a.airCraft.ServerSeed(),
		ClientSeed: clientSeed,
		BetId:      betIdStr,
		AutoMulti:  autoMultiple,
	}

	if !avtMarker.CheckBet(betN, betObj, avBet) {
		a.RetErr1(tb, response.ErrForbidden, constant.CmdBet, betIdStr)
		return
	}
	// 预设payout信息
	betObj.ImRet = &statement.PlayPayout{
		BetId:   betObj.BetId,
		GameAct: betObj.GameAct,
		GameId:  betObj.GameId,
		RoundId: currRoundId,
	}
	code := a.doSubmit(tb, betObj, avBet)
	if code != 0 {
		avtMarker.ClearIdxPlay(betIdStr)
		avtMarker.RemoveAutoCashOut(currRoundId, betIdStr)
		a.RetErrA(tb, code, constant.CmdBet, betIdStr, true)
		return
	}
	if !tb.Seat.Guest {
		a.airCraft.PlayAction(betObj.BetId, currRoundId, 1)
	}

	a.sendBetInfo(a.keyBet, betN)

	_bet, _ := stringutil.ToFloat64(betValue, 0)
	_betid, _ := stringutil.ToInt32(betIdStr, 0)
	msg := response.AcquireMsgO13()
	xw := sfs.XWriter{}
	defer xw.Reset()
	xw.WriteUShort(uint16(2))
	xw.WriteStringS(sfs.CmdKey, constant.CmdBet)
	xw.WriteSfsObjectPre(sfs.ParamId, uint16(8))
	xw.WriteShortS(sfs.KeyCode, response.CSuccess)
	xw.WriteDoubleS(constant.KeyBet, _bet)
	xw.WriteIntS(constant.KeyBetId, _betid)
	xw.WriteStringS(constant.KeyCurrency, tb.Seat.Currency)
	xw.WriteStringS(constant.KeyPlayerid, tb.Player.ShowID())
	xw.WriteStringS(constant.KeyUsername, tb.Player.ShowName())
	xw.WriteStringS(constant.KeyProfileImage, tb.Player.UHead())
	xw.WriteBoolS(constant.KeyIsFreeBet, betN.FreeBet)

	xw.WriteTo(msg)
	tb.Send2Client(msg)
}

func (a *GAviator) doSubmit(tb *games.GameTable, betObj *statement.PlayBet, avBet *statement.AviatorBetInfo) int16 {
	switch betObj.GameAct {
	case mcomm.GameAction_GameBet: // 投注
		betObj.Detail = a.avBetInfo(avBet)
		return tb.Bet(betObj)
	case mcomm.GameAction_GameCashOut: // CashOut
		betObj.ImRet.Detail = a.avBetInfo(avBet)
		return tb.Payout(betObj)
	case mcomm.GameAction_GameCancel: // 取消注单
		return tb.BetCancel(betObj)
	}

	return response.ErrInternal
}

func (a *GAviator) avBetInfo(avBet *statement.AviatorBetInfo) string {
	b, err := json.Marshal(avBet)
	if err != nil {
		logger.Error("[Aviator] do submit fail on json.Marshal aviator bet", zap.Error(err))
		return ""
	}
	return byteutil.SliceByteToString(b)
}

// sendBetInfo 发往avtower汇总
func (a *GAviator) sendBetInfo(key string, bn *aviatorp.BetNote) {
	if bn == nil {
		return
	}
	b, err := proto.Marshal(bn)
	if err != nil {
		logger.Error("[Aviator] send bet to control tower fail on proto.Marshal", zap.Int64("AgtId", bn.BetU.AgtId), zap.Uint64("Uid", bn.BetU.Uid), zap.Int64("InstID", a.GameInstId()), zap.Error(err))
		return
	}
	_ = gloader.CommMgr().NatsC().Publish(key, b)
}

func (a *GAviator) cancelBetHandler(tb *games.GameTable, exMsg *sfs.TSfsObject) {
	data := exMsg.GetContent()
	if data == nil {
		a.RetErr(tb, response.ErrBadRequest, constant.CmdCancelBet)
		return
	}
	data = data.GetSFSObject(sfs.ParamId)
	if data == nil {
		a.RetErr(tb, response.ErrBadRequest, constant.CmdCancelBet)
		return
	}
	betIdStr := data.GetUtfString(constant.KeyBetId)
	if betIdStr != BetIdx1 && betIdStr != BetIdx2 {
		a.RetErr1(tb, response.ErrBadRequest, constant.CmdCancelBet, betIdStr)
		return
	}

	canCancel, currRoundId := a.airCraft.CanBet()
	if !canCancel {
		a.RetErr1(tb, response.ErrRequestTimeout, constant.CmdCancelBet, betIdStr)
		return
	}
	avtMarker := tb.Marker.(*AviatorMarker)
	if !avtMarker.CheckCancel(betIdStr, currRoundId) {
		a.RetErr1(tb, response.ErrRequestTimeout, constant.CmdCancelBet, betIdStr)
		return
	}
	betN, betObj, avBet := avtMarker.GetBetNode(betIdStr)
	a.sendRoundAct(tb, betObj.BetId, betObj.Uid, currRoundId, dateutil.UnixMilli(), betIdStr, constant.AvtBetCancel, 0, 0)
	preAct := betObj.GameAct
	betObj.GameAct = mcomm.GameAction_GameCancel
	code := a.doSubmit(tb, betObj, avBet)
	if code != 0 {
		a.RetErrA(tb, code, constant.CmdCancelBet, betIdStr, true)
		avBet.Cancel = false
		betObj.GameAct = preAct
		return
	}
	avtMarker.RemoveAutoCashOut(currRoundId, betIdStr)
	avtMarker.ClearIdxPlay(betIdStr)
	if !tb.Seat.Guest {
		a.airCraft.PlayAction(betObj.BetId, currRoundId, 3)
	}

	if betN != nil {
		betN.Op = constant.AvtBetCancel
	}
	a.sendBetInfo(a.keyCancel, betN)

	_betid, _ := stringutil.ToInt32(betIdStr, 0)
	msg := response.AcquireMsgO13()
	xw := sfs.XWriter{}
	defer xw.Reset()
	xw.WriteUShort(uint16(2))
	xw.WriteStringS(sfs.CmdKey, constant.CmdCancelBet)
	xw.WriteSfsObjectPre(sfs.ParamId, uint16(4))
	xw.WriteShortS(sfs.KeyCode, response.CSuccess)
	xw.WriteIntS(constant.KeyBetId, _betid)
	xw.WriteStringS(constant.KeyPlayerid, tb.Player.ShowID())
	xw.WriteStringS(constant.KeyUsername, tb.Player.ShowName())

	xw.WriteTo(msg)
	tb.Send2Client(msg)
}

func (a *GAviator) cashOutHandler(tb *games.GameTable, exMsg *sfs.TSfsObject) {
	defer logger.RecoverPanic()
	data := exMsg.GetContent()
	if data == nil {
		a.RetErr(tb, response.ErrBadRequest, constant.CmdCashout)
		return
	}
	data = data.GetSFSObject(sfs.ParamId)
	if data == nil {
		a.RetErr(tb, response.ErrBadRequest, constant.CmdCashout)
		return
	}
	betIdStr := data.GetUtfString(constant.KeyBetId)
	if betIdStr != BetIdx1 && betIdStr != BetIdx2 {
		a.RetErr1(tb, response.ErrBadRequest, constant.CmdCashout, betIdStr)
		return
	}

	//clienttime := data.GetUtfString("currentTimestamp")
	//if betIdStr != "1" && betIdStr != "2" {
	//	a.RetErr(gmsg.CCashoutFail, CmdCashout, "invalid timestamp", false)
	//	return
	//}

	canOut, currRoundId, multi, maxMulti := a.airCraft.CanCashOut()
	if !canOut {
		a.RetErr1(tb, response.ErrForbidden, constant.CmdCashout, betIdStr)
		return
	}
	avtMarker := tb.Marker.(*AviatorMarker)
	if !avtMarker.CheckCashOut(betIdStr, currRoundId) {
		a.RetErr1(tb, response.ErrRequestTimeout, constant.CmdCashout, betIdStr)
		return
	}
	betN, betObj, avBet := avtMarker.GetBetNode(betIdStr)

	a.doCashOut(tb, avtMarker, betIdStr, betN, betObj, avBet, currRoundId, multi, maxMulti, false)
}

func (a *GAviator) doCashOut(tb *games.GameTable, avtMarker *AviatorMarker, betIdStr string, betN *aviatorp.BetNote,
	betObj *statement.PlayBet, avBet *statement.AviatorBetInfo, roundId int64, multi, maxMulti float64, auto bool) {

	t := dateutil.UnixMilli()
	a.sendRoundAct(tb, betObj.BetId, betObj.Uid, betObj.RoundId, t, betIdStr, constant.AvtBetCashOut, maxMulti, multi)
	d1, _ := decimal.NewFromString(betObj.BetAmt)
	dm := decimal.NewFromFloat(multi)
	ret := d1.Mul(dm).Truncate(tb.Seat.Precision)
	winAmt, win := tb.CheckMaxWin(ret.String())
	avBet.Multiplier = multi
	avBet.Result = maxMulti
	betObj.ImRet.Win = mcomm.GameResult_GameWin
	betObj.ImRet.Payout = dm.String()
	betObj.ImRet.WinAmt = winAmt
	betObj.ImRet.Dateline = t
	preAct := betObj.GameAct
	betObj.GameAct = mcomm.GameAction_GameCashOut
	betObj.ImRet.GameAct = betObj.GameAct
	code := a.doSubmit(tb, betObj, avBet)
	if code != 0 {
		a.RetErrA(tb, code, constant.CmdCashout, betIdStr, true)
		avBet.CashOut = false
		betObj.GameAct = preAct
		return
	}
	avtMarker.RemoveAutoCashOut(betObj.RoundId, betIdStr)
	avtMarker.ClearIdxPlay(betIdStr)
	if !tb.Seat.Guest && !auto {
		a.airCraft.PlayAction(betObj.BetId, roundId, 2)
	}
	if betN != nil {
		betN.Op = constant.AvtBetCashOut
		betN.Multiplier = multi
		betN.WinAmt = win
		a.sendBetInfo(a.keyCashOut, betN)
	}
	_betid, _ := stringutil.ToInt32(betIdStr, 1)
	_bet, _ := stringutil.ToFloat64(betObj.BetAmt, 0)
	msg := response.AcquireMsgO13()
	xw := sfs.XWriter{}
	defer xw.Reset()
	xw.WriteUShort(uint16(2))
	xw.WriteStringS(sfs.CmdKey, constant.CmdCashout)
	xw.WriteSfsObjectPre(sfs.ParamId, uint16(8))
	xw.WriteShortS(sfs.KeyCode, response.CSuccess)
	xw.WriteIntS(constant.KeyBetId, _betid)
	xw.WriteDoubleS(constant.KeyBetAmount, _bet)
	xw.WriteDoubleS(constant.KeyWinAmount, win)
	xw.WriteDoubleS(constant.KeyMultiplier, multi)
	xw.WriteStringS(constant.KeyCurrency, tb.Seat.Currency)
	xw.WriteStringS(constant.KeyPlayerid, tb.Player.ShowID())
	xw.WriteStringS(constant.KeyUsername, tb.Player.ShowName())

	xw.WriteTo(msg)
	tb.Send2Client(msg)
}

func (a *GAviator) doAutoCashOut(data *games.AutoCashOutCheckPoint) {
	avtMarker := data.Tb.Marker.(*AviatorMarker)
	autoBetInfo := avtMarker.GetAutoCashOut(data.RoundId, data.BetIdx)
	if autoBetInfo == nil || autoBetInfo.BetObj == nil || autoBetInfo.AvBet == nil {
		avtMarker.RemoveAutoCashOut(data.RoundId, data.BetIdx)
		return
	}
	a.doCashOut(data.Tb, avtMarker, data.BetIdx, autoBetInfo.BetN, autoBetInfo.BetObj, autoBetInfo.AvBet, data.RoundId,
		data.Multi, data.MaxMulti, true)
}

func (a *GAviator) sendRoundAct(tb *games.GameTable, betId, uid uint64, roundId, dateline int64, idx string, op int32, result, payout float64) {
	if tb.Seat.Guest {
		return
	}
	d := aviatorp.AvtRoundAct{
		BetId:    betId,
		RoundId:  roundId,
		Idx:      idx,
		Op:       op,
		Uid:      uid,
		Dateline: dateline,
		Result:   result,
		Payout:   payout,
	}
	b, err := proto.Marshal(&d)
	if err != nil {
		logger.Error("[Aviator] send round act control tower fail on proto.Marshal", zap.Uint64("BetId", betId), zap.Int64("RoundId", roundId), zap.Int64("InstID", a.GameInstId()), zap.Error(err))
		return
	}
	_ = gloader.CommMgr().NatsC().Publish(a.keyAct, b)
}

func (a *GAviator) roundFairnessHandler(tb *games.GameTable, exMsg *sfs.TSfsObject) {
	data := exMsg.GetContent()
	if data == nil {
		a.RetErr(tb, response.ErrBadRequest, constant.CmdRoundFairness)
		return
	}
	data = data.GetSFSObject(sfs.ParamId)
	if data == nil {
		a.RetErr(tb, response.ErrBadRequest, constant.CmdRoundFairness)
		return
	}
	roundIdStr := data.GetUtfString(constant.KeyRoundId)
	roundId, _ := stringutil.ToInt64(roundIdStr, 0)
	if !gvalidator.IsInt(roundIdStr) || roundId <= 0 {
		a.RetErr(tb, response.ErrBadRequest, constant.CmdRoundFairness)
		return
	}

	req := &aviatorp.AvtRoundFairnessReq{
		RoundId: roundId,
		InstId:  tb.InstID(),
	}
	reply := &aviatorp.AvtRoundFairnessRes{}
	rpcCtx, cancelFn := context.WithTimeout(context.Background(), 15*time.Second)
	err := client.GetBetTowerClient().Call(rpcCtx, "AviatorRoundFairness", req, reply)
	cancelFn()
	if err != nil {
		logger.Error("[Aviator] query aviator round fairness fail", zap.Int64("AgtId", tb.Player.AgtID()), zap.Uint64("Uid", tb.Player.ID()), zap.Int64("InstID", a.GameInstId()), zap.Int64("RoundId", roundId), zap.Error(err))
		a.RetErr(tb, response.ErrInternal, constant.CmdRoundFairness)
		return
	}
	if reply.Code != 0 {
		a.RetErr(tb, int16(reply.Code), constant.CmdRoundFairness)
		return
	}

	msg := response.AcquireMsgO13()
	xw := sfs.XWriter{}
	defer xw.Reset()
	xw.WriteUShort(uint16(2))
	xw.WriteStringS(sfs.CmdKey, constant.CmdRoundFairness)
	xw.WriteSfsObjectPre(sfs.ParamId, uint16(2))
	xw.WriteShortS(sfs.KeyCode, response.CSuccess)
	xw.WriteSfsObjectPre(constant.KeyFairness, uint16(8))
	xw.WriteLongS(constant.KeyRoundId, reply.Data.RoundId)
	xw.WriteLongS(constant.KeyRoundStartDate, reply.Data.Start)
	xw.WriteStringS(constant.KeyPartSeedHexNumber, reply.Data.Hhex)
	xw.WriteLongS(constant.KeyPartSeedDecimalNumber, reply.Data.Hdecimal)
	xw.WriteDoubleS(constant.KeyResult, reply.Data.Result)
	xw.WriteStringS(constant.KeyServerSeed, reply.Data.Seed)
	xw.WriteStringS(constant.KeySeedSHA256, reply.Data.Seed256)

	xw.WriteSfsArrayPre(constant.KeyPlayerSeeds, uint16(len(reply.Data.First3.GetFirst3())))
	for _, betU := range reply.Data.First3.GetFirst3() {
		xw.WriteUByte(sfs.SfsObject)
		xw.WriteUShort(uint16(3))
		xw.WriteStringS(constant.KeySeed, betU.Seed)
		xw.WriteStringS(constant.KeyProfileImage, betU.Uhead)
		xw.WriteStringS(constant.KeyUsername, betU.Uname)
	}

	xw.WriteTo(msg)
	tb.Send2Client(msg)
}

func (a *GAviator) getBetHistory(tb *games.GameTable, m *sfs.TSfsObject) {
	limit, offset := a.GetPageLimit(m)
	if limit == 0 {
		return
	}

	tb.GetUserBets(offset, limit)
}
